package com.zjun.widget;

import static ohos.multimodalinput.event.TouchEvent.POINT_MOVE;
import static ohos.multimodalinput.event.TouchEvent.PRIMARY_POINT_DOWN;
import static ohos.multimodalinput.event.TouchEvent.PRIMARY_POINT_UP;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentTreeObserver;
import ohos.agp.components.ScrollHelper;
import ohos.agp.components.VelocityDetector;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.render3d.BuildConfig;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.agp.utils.Rect;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.multimodalinput.event.TouchEvent;

/**
 * GradationView
 * 刻度卷尺控件
 * <p>
 * 思路：
 * 1. 把float类型数据，乘10，转为int类型。之所以把它放在第一位，因为踩过这个坑：不转的话，由于精度问题，会导致计算异常复杂
 * 2. 绘制刻度：
 * - 根据中间指针位置的数值，来计算最小值位置与中间指针位置的距离
 * - 为了绘制性能，只绘制控件宽度范围内的刻度。但不出现数值突变（两侧刻度出现突然显示或不显示），两侧各增加2个单位
 * 3. 滑动时，通过移动最小位置与中间指针位置的距离，逆向推算当前刻度值
 * 4. 滑动停止后，自动调整到最近的刻度：使用滑动器Scroller，需要计算出最终要抵达的位置
 * 5. 惯性滑动：使用速度跟踪器VelocityTracker
 * <p>
 * Author: Ralap
 * Description:
 * Date 2018/7/29
 */
public class RuleView extends Component {
    private static final boolean LOG_ENABLE = BuildConfig.DEBUG;

    /**
     * 滑动阈值
     */
    private final int TOUCH_SLOP;
    /**
     * 惯性滑动最小、最大速度
     */
    private final int MIN_FLING_VELOCITY;

    private final int MAX_FLING_VELOCITY;

    /**
     * 背景色
     */
    private int bgColor;
    /**
     * 刻度颜色
     */
    private int gradationColor;
    /**
     * 短刻度线宽度
     */
    private float shortLineWidth;
    /**
     * 长刻度线宽度
     * 默认 = 2 * shortLineWidth
     */
    private float longLineWidth;
    /**
     * 短刻度长度
     */
    private float shortGradationLen;
    /**
     * 长刻度长度
     * 默认为短刻度的2倍
     */
    private float longGradationLen;
    /**
     * 刻度字体颜色
     */
    private int textColor;
    /**
     * 刻度字体大小
     */
    private float textSize;
    /**
     * 中间指针线颜色
     */
    private int indicatorLineColor;
    /**
     * 中间指针线宽度
     */
    private float indicatorLineWidth;
    /**
     * 中间指针线长度
     */
    private float indicatorLineLen;
    /**
     * 最小值
     */
    private float minValue;
    /**
     * 最大值
     */
    private float maxValue;
    /**
     * 当前值
     */
    private float currentValue;
    /**
     * 刻度最小单位
     */
    private float gradationUnit;
    /**
     * 需要绘制的数值
     */
    private int numberPerCount;
    /**
     * 刻度间距离
     */
    private float gradationGap;
    /**
     * 刻度与文字的间距
     */
    private float gradationNumberGap;

    /**
     * 最小数值，放大10倍：minValue * 10
     */
    private int mMinNumber;
    /**
     * 最大数值，放大10倍：maxValue * 10
     */
    private int mMaxNumber;
    /**
     * 当前数值
     */
    private int mCurrentNumber;
    /**
     * 最大数值与最小数值间的距离：(mMaxNumber - mMinNumber) / mNumberUnit * gradationGap
     */
    private float mNumberRangeDistance;
    /**
     * 刻度数值最小单位：gradationUnit * 10
     */
    private int mNumberUnit;
    /**
     * 当前数值与最小值的距离：(mCurrentNumber - minValue) / mNumberUnit * gradationGap
     */
    private float mCurrentDistance;
    /**
     * 控件宽度所占有的数值范围：mWidth / gradationGap * mNumberUnit
     */
    private int mWidthRangeNumber;

    /**
     * 普通画笔
     */
    private Paint mPaint;
    /**
     * 文字画笔
     */
    private Paint mTextPaint;
    /**
     * 滑动器
     */
    private ScrollHelper mScroller;
    /**
     * 速度跟踪器
     */
    private VelocityDetector mVelocityTracker;
    /**
     * 尺寸
     */
    private int mWidth;

    private int mHalfWidth;
    private int mHeight;

    private int mDownX;
    private int mLastX;
    private int mLastY;
    private boolean isMoved;

    private OnValueChangedListener mValueChangedListener;

    /**
     * 当前值变化监听器
     */
    public interface OnValueChangedListener {
        void onValueChanged(float value);
    }

    public RuleView(Context context) {
        this(context, null);
    }

    public RuleView(Context context, AttrSet attrSet) {
        this(context, attrSet, "");
    }

    public RuleView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        initAttrs(attrSet);

        // 初始化final常量，必须在构造中赋初值
        TOUCH_SLOP = 8;
        MIN_FLING_VELOCITY = 50;
        MAX_FLING_VELOCITY = 8000;

        convertValue2Number();
        init(context);
        addDrawTask(
                new DrawTask() {
                    @Override
                    public void onDraw(Component component, Canvas canvas) {
                        mWidth = component.getWidth();
                        mHeight = dp2px(60);
                        mHalfWidth = mWidth >> 1;
                        if (mWidthRangeNumber == 0) {
                            mWidthRangeNumber = (int) (mWidth / gradationGap * mNumberUnit);
                        }
                        setWidth(mWidth);
                        setHeight(mHeight);
                        Paint paintBg = new Paint();
                        paintBg.setColor(new Color(bgColor));
                        RectFloat rect = new RectFloat(0, 0, mWidth, mHeight);
                        canvas.drawRect(rect, paintBg);
                        // 1 绘制背景色
                        canvas.drawColor(bgColor, Canvas.PorterDuffMode.DST);
                        // 2 绘制刻度、数字
                        drawGradation(canvas);
                        // 3 绘制指针
                        drawIndicator(canvas);
                    }
                });

        setTouchEventListener(
                new TouchEventListener() {
                    @Override
                    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
                        final int action = touchEvent.getAction();
                        final int xCoordinate = (int) touchEvent.getPointerPosition(0).getX();
                        final int yCoordinate = (int) touchEvent.getPointerPosition(0).getY();

                        if (mVelocityTracker == null) {
                            mVelocityTracker = VelocityDetector.obtainInstance();
                        }
                        mVelocityTracker.addEvent(touchEvent);
                        switch (action) {
                            case PRIMARY_POINT_DOWN:
                                mDownX = xCoordinate;
                                isMoved = false;
                                System.out.println("lalalalallalaPRIMARY_POINT_DOWN");
                                break;
                            case POINT_MOVE:
                                final int dx = xCoordinate - mLastX;
                                int xVel = (int) mVelocityTracker.getHorizontalVelocity();
                                // 判断是否已经滑动
                                if (!isMoved) {
                                    final int dy = yCoordinate - mLastY;
                                    // 滑动的触发条件：水平滑动大于垂直滑动；滑动距离大于阈值
                                    if (Math.abs(dx) < Math.abs(dy) || Math.abs(xCoordinate - mDownX) < TOUCH_SLOP) {
                                        break;
                                    }
                                    isMoved = true;
                                }

                                mCurrentDistance += -dx;
                                calculateValue();
                                System.out.println("lalalalallala");

                                break;
                            case PRIMARY_POINT_UP:
                                LogUtil.info("onTouchEvent: action=%d", action + "");
                                // 计算速度：使用1000ms为单位
                                mVelocityTracker.calculateCurrentVelocity(1000, MAX_FLING_VELOCITY, MAX_FLING_VELOCITY);
                                // 获取速度。速度有方向性，水平方向：左滑为负，右滑为正
                                int xVelocity = (int) mVelocityTracker.getHorizontalVelocity();
                                // 达到速度则惯性滑动，否则缓慢滑动到刻度
                                if (Math.abs(xVelocity) >= MIN_FLING_VELOCITY) {
                                    // 速度具有方向性，需要取反
                                    mScroller.doFling((int) mCurrentDistance, 0, -xVelocity, 0, 0,
                                            (int) mNumberRangeDistance, 0,
                                            0);

                                    invalidate();
                                    scrollToGradation();
                                } else {
                                    LogUtil.info("fling-----PRIMARY_POINT_UP", "lalalal");
                                    scrollToGradation();
                                }

                                scrollToGradation();
                                break;
                            default:
                                break;
                        }
                        mLastX = xCoordinate;
                        mLastY = yCoordinate;
                        return true;
                    }
                });
    }

    private void initAttrs(AttrSet attrSet) {
        bgColor = AttrUtils.getColorFromAttr(attrSet, "zjun_bgColor", Color.getIntColor("#f5f8f5"));
        gradationColor = AttrUtils.getColorFromAttr(attrSet, "zjun_gradationColor", Color.LTGRAY.getValue());
        shortLineWidth = AttrUtils.getDimensionFromAttr(attrSet, "gv_shortLineWidth", dp2px(1));
        shortGradationLen = AttrUtils.getDimensionFromAttr(attrSet, "gv_shortGradationLen", dp2px(16));
        longGradationLen =
                AttrUtils.getDimensionFromAttr(attrSet, "gv_longGradationLen", (int) (shortGradationLen * 2));
        longLineWidth = AttrUtils.getDimensionFromAttr(attrSet, "gv_longLineWidth", (int) (shortLineWidth * 2));
        textColor = AttrUtils.getColorFromAttr(attrSet, "zjun_textColor", Color.BLACK.getValue());
        textSize = AttrUtils.getDimensionFromAttr(attrSet, "zjun_textSize", sp2px(14));
        indicatorLineColor =
                AttrUtils.getColorFromAttr(attrSet, "zjun_indicatorLineColor", Color.getIntColor("#48b975"));
        indicatorLineWidth = AttrUtils.getDimensionFromAttr(attrSet, "zjun_indicatorLineWidth", dp2px(3f));
        indicatorLineLen = AttrUtils.getDimensionFromAttr(attrSet, "gv_indicatorLineLen", dp2px(35f));
        minValue = AttrUtils.getFloatFromAttr(attrSet, "gv_minValue", 0f);
        maxValue = AttrUtils.getFloatFromAttr(attrSet, "gv_maxValue", 100f);
        currentValue = AttrUtils.getFloatFromAttr(attrSet, "gv_currentValue", 50f);
        gradationUnit = AttrUtils.getFloatFromAttr(attrSet, "gv_gradationUnit", .1f);
        numberPerCount = AttrUtils.getIntFromAttr(attrSet, "gv_numberPerCount", 10);
        gradationGap = AttrUtils.getDimensionFromAttr(attrSet, "gv_gradationGap", dp2px(10));
        gradationNumberGap = AttrUtils.getDimensionFromAttr(attrSet, "gv_gradationNumberGap", dp2px(8));
    }


    /**
     * Init *
     *
     * @param context context
     */
    private void init(Context context) {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(shortLineWidth);

        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextSize((int) textSize);
        mTextPaint.setColor(new Color(textColor));

        mScroller = new ScrollHelper();
    }

    /**
     * 把真实数值转换成绘制数值
     * 为了防止float的精度丢失，把minValue、maxValue、currentValue、gradationUnit都放大10倍
     */
    private void convertValue2Number() {
        mMinNumber = (int) (minValue * 10);
        mMaxNumber = (int) (maxValue * 10);
        mCurrentNumber = (int) (currentValue * 10);
        mNumberUnit = (int) (gradationUnit * 10);
        mCurrentDistance = (mCurrentNumber - mMinNumber) / mNumberUnit * gradationGap;
        mNumberRangeDistance = (mMaxNumber - mMinNumber) / mNumberUnit * gradationGap;
        if (mWidth != 0) {
            // 初始化时，在onMeasure()里计算
            mWidthRangeNumber = (int) (mWidth / gradationGap * mNumberUnit);
        }
    }



    /**
     * 计算宽度或高度的真实大小
     * <p>
     * 宽或高为wrap_content时，父控件的测量模式无论是EXACTLY还是AT_MOST，默认给的测量模式都是AT_MOST，测量大小为父控件的size
     * 所以，我们宽度不管，只处理高度，默认80dp
     *
     * @param isWidth 是不是宽度
     * @param spec    测量规则
     * @return 真实的大小
     */
    private int calculateSize(boolean isWidth, int spec) {
        final int mode = MeasureSpec.getMode(spec);
        final int size = MeasureSpec.getSize(spec);

        int realSize = size;
        switch (mode) {
            // 精确模式：已经确定具体数值：layout_width为具体值，或match_parent
            case MeasureSpec.PRECISE:
                break;
            // 最大模式：最大不能超过父控件给的widthSize：layout_width为wrap_content
            case MeasureSpec.NOT_EXCEED:
                if (!isWidth) {
                    int defaultContentSize = dp2px(80);
                    realSize = Math.min(realSize, defaultContentSize);
                }
                break;
            // 未指定尺寸模式：一般父控件是AdapterView
            case MeasureSpec.UNCONSTRAINT:
            default:
        }
        logD("isWidth=%b, mode=%d, size=%d, realSize=%d", isWidth, mode, size, realSize);
        return realSize;
    }


    EventHandler eventHandler = new EventHandler(EventRunner.getMainEventRunner());

    /**
     * 根据distance距离，计算数值
     */
    private void calculateValue() {
        // 限定范围：在最小值与最大值之间
        mCurrentDistance = Math.min(Math.max(mCurrentDistance, 0), mNumberRangeDistance);
        mCurrentNumber = mMinNumber + (int) (mCurrentDistance / gradationGap) * mNumberUnit;
        currentValue = mCurrentNumber / 10f;
        if (mValueChangedListener != null) {
            mValueChangedListener.onValueChanged(currentValue);
        }
        eventHandler.postSyncTask(
                new Runnable() {
                    @Override
                    public void run() {
                        invalidate();
                    }
                });
    }

    /**
     * 滑动到最近的刻度线上
     */
    private void scrollToGradation() {
        mCurrentNumber = mMinNumber + Math.round(mCurrentDistance / gradationGap) * mNumberUnit;
        mCurrentNumber = Math.min(Math.max(mCurrentNumber, mMinNumber), mMaxNumber);
        mCurrentDistance = (mCurrentNumber - mMinNumber) / mNumberUnit * gradationGap;
        currentValue = mCurrentNumber / 10f;
        logD(
                "scrollToGradation: mCurrentDistance=%f, mCurrentNumber=%d, currentValue=%f",
                mCurrentDistance, mCurrentNumber, currentValue);
        if (mValueChangedListener != null) {
            mValueChangedListener.onValueChanged(currentValue);
        }
        invalidate();
    }

    /**
     * 滑动到最近的刻度线上
     *
     * @param currentNumber 当前数
     */
    private void scrollToGradation(int currentNumber) {
        mCurrentNumber = currentNumber;
        mCurrentDistance = (mCurrentNumber - mMinNumber) / mNumberUnit * gradationGap;
        currentValue = mCurrentNumber / 10f;
        logD(
                "scrollToGradation: mCurrentDistance=%f, mCurrentNumber=%d, currentValue=%f",
                mCurrentDistance, mCurrentNumber, currentValue);
        if (mValueChangedListener != null) {
            mValueChangedListener.onValueChanged(currentValue);
        }
        invalidate();
    }

    /**
     * 绘制刻度
     *
     * @param canvas 画布
     */
    private void drawGradation(Canvas canvas) {
        // 1 顶部基准线
        mPaint.setColor(new Color(gradationColor));
        mPaint.setStrokeWidth(shortLineWidth);
        Point startPoint = new Point(0, shortLineWidth * .5f);
        Point endPoint = new Point(mWidth, 0);
        canvas.drawLine(startPoint, endPoint, mPaint);

        /*
         * 2 左侧刻度
         * 2.1 计算左侧开始绘制的刻度
         */
        int startNum = ((int) mCurrentDistance - mHalfWidth) / (int) gradationGap * mNumberUnit + mMinNumber;
        // 扩展2个单位
        final int expendUnit = mNumberUnit << 1;
        // 左侧扩展
        startNum -= expendUnit;
        if (startNum < mMinNumber) {
            startNum = mMinNumber;
        }
        // 右侧扩展
        int rightMaxNum = (startNum + expendUnit) + mWidthRangeNumber + expendUnit;
        if (rightMaxNum > mMaxNumber) {
            rightMaxNum = mMaxNumber;
        }
        // 当前绘制刻度对应控件左侧的位置
        float distance = mHalfWidth - (mCurrentDistance - (startNum - mMinNumber) / mNumberUnit * gradationGap);
        final int perUnitCount = mNumberUnit * numberPerCount;
        logD("drawGradation: startNum=%d, rightNum=%d, perUnitCount=%d", startNum, rightMaxNum, perUnitCount);
        while (startNum <= rightMaxNum) {
            logD("drawGradation: startNum=%d", startNum);
            if (startNum % perUnitCount == 0) {
                // 长刻度：刻度宽度为短刻度的2倍
                mPaint.setStrokeWidth(longLineWidth);
                Point startPointGradation = new Point(distance, 0);
                Point endPointGradation = new Point(distance, longGradationLen);
                canvas.drawLine(startPointGradation, endPointGradation, mPaint);

                // 数值
                float fNum = startNum / 10f;
                String text = Float.toString(fNum);
                logD("drawGradation: text=%s", text);
                if (text.endsWith(".0")) {
                    text = text.substring(0, text.length() - 2);
                }
                final float textWidth = mTextPaint.measureText(text);
                canvas.drawText(
                        mTextPaint, text, distance - textWidth * .5f, longGradationLen + gradationNumberGap + textSize);
            } else {
                // 短刻度
                mPaint.setStrokeWidth(shortLineWidth);
                Point startPointGradation = new Point(distance, 0);
                Point endPointGradation = new Point(distance, shortGradationLen);
                canvas.drawLine(startPointGradation, endPointGradation, mPaint);
            }
            startNum += mNumberUnit;
            distance += gradationGap;
        }
    }

    /**
     * 绘制指针
     *
     * @param canvas 画布
     */
    private void drawIndicator(Canvas canvas) {
        mPaint.setColor(new Color(indicatorLineColor));
        mPaint.setStrokeWidth(indicatorLineWidth);
        // 圆头画笔
        mPaint.setStrokeCap(Paint.StrokeCap.ROUND_CAP);
        Point startPoint = new Point(mHalfWidth, 0);
        Point endPoint = new Point(mHalfWidth, indicatorLineLen);
        canvas.drawLine(startPoint, endPoint, mPaint);
        // 默认形状画笔
        mPaint.setStrokeCap(Paint.StrokeCap.BUTT_CAP);
    }

    /**
     * dp转化为px
     *
     * @param dp dp
     * @return 转换后的值
     */
    private int dp2px(float dp) {
        return DensityUtil.dp2px(getContext(), dp);
    }


    /**
     * Sp 2 px int
     *
     * @param sp sp
     * @return the int
     */
    private int sp2px(float sp) {
        return DensityUtil.dp2px(getContext(), sp);
    }

    /**
     * 打印日志
     *
     * @param format tag
     * @param args   文本
     */
    @SuppressWarnings("all")
    private void logD(String format, Object... args) {
        if (LOG_ENABLE) {
            LogUtil.info("GradationView", String.format("zjun@" + format, args));
        }
    }

    /**
     * 设置新的当前值
     *
     * @param currentValue 当前值
     */
    public void setCurrentValue(float currentValue) {
        if (currentValue < minValue || currentValue > maxValue) {
            throw new IllegalArgumentException(

                    "The currentValue of "+ currentValue+" is out of range: [" + minValue+"," + maxValue+"]");

        }
        if (!mScroller.isFinished()) {
            mScroller.isOverScrolled();
        }
        this.currentValue = currentValue;
        mCurrentNumber = (int) (this.currentValue * 10);
        final float newDistance = (mCurrentNumber - mMinNumber) / mNumberUnit * gradationGap;
        final int dx = (int) (newDistance - mCurrentDistance);
        // 最大2000ms
        final int duration = dx * 2000 / (int) mNumberRangeDistance;
        // 滑动到目标值
        mScroller.startScroll((int) mCurrentDistance, 0, dx, duration);
        scrollToGradation((int) mCurrentNumber);
    }

    /**
     * 获取最小值
     *
     * @return 最小值
     */
    public float getMinValue() {
        return minValue;
    }

    public float getMaxValue() {
        return maxValue;
    }

    /**
     * 获取当前值
     *
     * @return 当前值
     */
    public float getCurrentValue() {
        return this.currentValue;
    }

    /**
     * 重新配置参数
     *
     * @param //minValue 最小值
     * @param //maxValue 最大值
     * @param //curValue 当前值
     * @param //unit     最小单位所代表的值
     * @param //perCount 相邻两条长刻度线之间被分成的隔数量
     */
    public void setValue(float minValue, float maxValue, float curValue, float unit, int perCount) {
        if (minValue > maxValue || curValue < minValue || curValue > maxValue) {
            throw new IllegalArgumentException(
                    String.format(
                            "The given values are invalid, check firstly: " + "minValue=%f, maxValue=%f, curValue=%s",
                            minValue, maxValue, curValue));
        }
        if (!mScroller.isFinished()) {
            mScroller.abortAnimation();
        }
        this.minValue = minValue;
        this.maxValue = maxValue;
        this.currentValue = curValue;
        this.gradationUnit = unit;
        this.numberPerCount = perCount;
        convertValue2Number();
        if (mValueChangedListener != null) {
            mValueChangedListener.onValueChanged(currentValue);
        }
        new EventHandler(EventRunner.getMainEventRunner())
                .postTask(
                        new Runnable() {
                            @Override
                            public void run() {
                                invalidate();
                            }
                        });
    }


    /**
     * Sets on value changed listener *
     *
     * @param listener listener
     */
    public void setOnValueChangedListener(OnValueChangedListener listener) {
        this.mValueChangedListener = listener;
    }
}
